package com.comunity_cloud.client.model.xmpp;


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.auth.AuthenticationException;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.packet.XMPPError.Type;
import org.jivesoftware.smack.provider.PrivacyProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.provider.DataFormProvider;
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
import org.jivesoftware.smackx.provider.MUCOwnerProvider;
import org.jivesoftware.smackx.provider.MUCUserProvider;
import org.jivesoftware.smackx.provider.VCardProvider;

import com.comunity_cloud.rest.RestClient;

public class XMPPClient implements PacketListener, MessageListener, RosterListener, ConnectionListener{
	private static final String LOG_ID = "XMPPClient";

	public static final String JABBER_SERVICE_DOMAIN = ".alex-laptop";
	public static final String JABBER_DOMAIN = "@alex-laptop";
	private static final String JABBER_HOST = "alex-laptop";

	private XMPPConnection connection;

	private Map<String,Chat> chats = new HashMap<String, Chat>();

	
	private XMPPPresenceListener presenceListener;
	
	private XMPPChatListener chatListener;
	
	private XMPPChatListener adminMessagesListener;
	
	private XMPPConnectionListener connectionListener;
	
	public enum PresenceMode{
		ONLINE,
		OFFLINE,
		BUSY,
		BERIGHTBACK,
		AWAY,
		ONTHEPHONE,
		OUTTOLUNCH,
		DONOTDISTURB,
		STATUSMOVED,
		STATUSALTSERVICE,
		PENDING
	}

	public XMPPClient(XMPPPresenceListener presenceListener, XMPPChatListener chatListener, XMPPChatListener adminMessagesListener) {
		this.chatListener = chatListener;
		this.presenceListener = presenceListener;
		this.adminMessagesListener = adminMessagesListener;
	}
	
	public void login(String userName, String password) throws XMPPException, AuthenticationException{
		log(LOG_ID, "Init");

		if (connection != null) {
			new Thread() {
				public void run() {
					
					log(LOG_ID, "Disconnecting old XMPP connection");
					//connection.removePacketListener(MessengerModel.this);
					try {
						chats.clear();
						connection.disconnect();
						log(LOG_ID, "Disconnected old XMPP connection");
					} catch (Exception e) {}
				};
			}.start();
		}

		log(LOG_ID, "Creating XMPP connection");
		ConnectionConfiguration connConfig = new ConnectionConfiguration(JABBER_HOST, 5222);
		connConfig.setSASLAuthenticationEnabled(false);
		connConfig.setSecurityMode(SecurityMode.required);
		connConfig.setSelfSignedCertificateEnabled(true);
		connection = new XMPPConnection(connConfig);

		log(LOG_ID, "Connecting");

		connection.connect();

		connection.addPacketListener(this, new PacketTypeFilter(Presence.class));
		connection.getChatManager().addChatListener(new ChatManagerListener() {
			public void chatCreated(Chat chat, boolean arg1){
				chat.addMessageListener(XMPPClient.this);
			}
		});


		log(LOG_ID, "Logging into Jabber");
		try {
			connection.login(userName, password);
			log(LOG_ID, "Logged into Jabber");
		} catch (XMPPException e) {
			XMPPError error = e.getXMPPError();
			if (error != null && error.getType() == Type.AUTH) {
				throw new AuthenticationException("XMPP login request is not authorised" , e);
			}
			else {
				throw e;
			}
		}

		connection.addConnectionListener(this);
		Roster roster = connection.getRoster();
		roster.setSubscriptionMode(org.jivesoftware.smack.Roster.SubscriptionMode.manual);
		roster.addRosterListener(this);	    

		new ServiceDiscoveryManager(connection);
		ProviderManager.getInstance().addExtensionProvider("x", "jabber:x:data", new DataFormProvider());
		ProviderManager.getInstance().addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider());
		ProviderManager.getInstance().addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider());
		ProviderManager.getInstance().addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
		ProviderManager.getInstance().addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
		ProviderManager.getInstance().addIQProvider("vCard", "vcard-temp", new VCardProvider());
		ProviderManager.getInstance().addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider());
		presenceListener.onRosterUpdated();
	}

	@Override
	public void processPacket(Packet p) {

		try {
			Presence presence = (Presence)p;
			if (presence.getType() == org.jivesoftware.smack.packet.Presence.Type.subscribe) {

				Collection<RosterEntry> entries = connection.getRoster().getEntries();
				String fromName = presence.getFrom().substring(0, presence.getFrom().indexOf('@'));


				for (RosterEntry rosterEntry : entries) {
					if(rosterEntry.getUser().substring(0, rosterEntry.getUser().indexOf('@')).equals(fromName)) {
						acceptFriendship(fromName);
						return;
					}
				}


				presenceListener.subscribeRequest(fromName);


			} 
		} catch (XMPPException e) {

		}
	}

	public void acceptFriendship(String user) throws XMPPException
	{
		Presence p = new Presence(org.jivesoftware.smack.packet.Presence.Type.subscribed, "", 1, org.jivesoftware.smack.packet.Presence.Mode.available);
		p.setTo(convertUsernameToAddress(user));
		connection.sendPacket(p);
		addFriend(user);
	}

	public void addFriend(String username) throws XMPPException
	{

		connection.getRoster().createEntry(convertUsernameToAddress(username), username, null);

	}

	public void removeFriend(String username) throws XMPPException
	{
		RosterEntry user = connection.getRoster().getEntry(convertUsernameToAddress(username));
		if (user == null) {
			log("MessengerModel", "Failed to remove friend '" + username + "': no such user");
			return;
		}

		connection.getRoster().removeEntry(user);
	}

	private String convertUsernameToAddress(String username) {
		return username + JABBER_DOMAIN;	
	}

	@Override
	public void processMessage(Chat chat, Message message) {
		String userName = getNameFromUri(message.getFrom());
		if(userName.equals("admin")) {
			adminMessagesListener.messageReceived(userName, message.getBody());
		}else{
			chatListener.messageReceived(userName, message.getBody());
		}
		if(!chats.containsKey(userName)) {
			chats.put(userName, chat);
		}
		
	}
	
	public void declineFriendship(String userName){
		
		Presence response = new Presence(Presence.Type.unsubscribed);
        response.setTo(userName);
        connection.sendPacket(response);
	}
	
	private String getNameFromUri(String uri) {
		return uri.substring(0,uri.indexOf("@"));
	}
	
	@Override
    public void presenceChanged(Presence presencePacket)
    {
    	log("MessengerModel", "presenceChanged event");
        PresenceMode presence;
        if(presencePacket.isAvailable()) {
        	presence = PresenceMode.ONLINE;
        } else {
        	presence = PresenceMode.OFFLINE;
        }
        presenceListener.presenceChanged(getNameFromUri(presencePacket.getFrom()),presence);        	
        
    }
	
	private Collection<String> convertAddressesToUsernames(Collection<String> addresses) {
		List<String> usernames = new ArrayList<String>(addresses.size());
		for (String address : addresses) {
			usernames.add(getNameFromUri(address));
		}
		return usernames;
	}
	
	@Override
	public void entriesAdded(Collection<String> addresses)
    {
		log("MessengerModel", "entriesAdded event");
		Collection<String> usernames = convertAddressesToUsernames(addresses);
		for (final String username : usernames) {

			// add to list and notify
			presenceListener.entryAdded(username);
			
		}
    }

    @Override
    public void entriesDeleted(Collection<String> addresses)
    {
    	log("MessengerModel", "entriesDeleted event");
    	Collection<String> usernames = convertAddressesToUsernames(addresses);
        for (String userName : usernames) {
        	presenceListener.entryDeleted(userName);
		}
    }

    @Override
    public void entriesUpdated(Collection<String> addresses)
    {
    	log("MessengerModel", "entriesUpdated event");
    	Collection<String> usernames = convertAddressesToUsernames(addresses);
    	for (String userName : usernames) {
    		presenceListener.entryUpdated(userName);
    	}
    }
    
    public void sendMessage(String userName, String body) throws XMPPException {
        
        log(LOG_ID, "Sending chat message to " + userName);
        Chat opponentChat = chats.get(userName);
        if(opponentChat == null) {
        	opponentChat = connection.getChatManager().createChat(convertUsernameToAddress(userName), this);
        	chats.put(userName, opponentChat);
        }
        Message packet = new Message();
        packet.setTo(convertUsernameToAddress(userName));
        packet.setBody(body);
        opponentChat.sendMessage(packet);
        
    }
    
    public void logout() {
    	
    	if (connection != null && connection.isConnected()) {
			connection.disconnect();
			chats.clear();
		}
    }

	public List<String> getFriends() {
		List<String> friends = new ArrayList<String>();
		Collection<RosterEntry> entries = connection.getRoster().getEntries();
		for (RosterEntry rosterEntry : entries) {
			friends.add(getNameFromUri(rosterEntry.getUser()));
		}
		return friends;
	}

	public void destroy() {
		connection.disconnect();
	}

	public boolean isAuthorized() {
		if(connection != null && connection.isAuthenticated()) {
			return true;
		}
		return false;
	}
	
	public void setConnectionListener(XMPPConnectionListener connectionListener) {
		this.connectionListener = connectionListener;
	}

	@Override
	public void connectionClosed() {
		if(connectionListener != null){
		
			connectionListener.onConnectionLost();
		
		}
	}

	@Override
	public void connectionClosedOnError(Exception arg0) {
		if(connectionListener != null){
			connectionListener.onConnectionLost();
		}
	}

	@Override
	public void reconnectingIn(int arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void reconnectionFailed(Exception arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void reconnectionSuccessful() {
		if(connectionListener != null){
			connectionListener.onConnectionGained();
		}
	}

	public PresenceMode getPresence(String friendName) {
		Presence presence = connection.getRoster().getPresence(convertUsernameToAddress(friendName));
		if(presence.isAvailable()) {
			return PresenceMode.ONLINE;
		} 
		return PresenceMode.OFFLINE;
	}
	
	public static void main(String[] args) {
		XMPPClient xmppClient = new XMPPClient(new XMPPPresenceListener() {
			
			@Override
			public void subscribeRequest(String userName) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void presenceChanged(String userName, PresenceMode presence) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onRosterUpdated() {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void entryUpdated(String userName) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void entryDeleted(String usernames) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void entryAdded(String username) {
				// TODO Auto-generated method stub
				
			}
		}, new XMPPChatListener() {
			
			@Override
			public void messageReceived(String from, String body) {
				System.out.println(body);
			}
		}, new XMPPChatListener() {
			
			@Override
			public void messageReceived(String from, String body) {
				System.out.println(body);
			}
		});
		
		try {
			xmppClient.login("alex4", RestClient.digest2HexString(RestClient.SHA256("zxc")));
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			Thread.sleep(100000);
		} catch (InterruptedException e) {}
	}
	
	private void log(String id, String msg){
		System.out.println(id+": "+msg);
	}
}
